
/*
This file is part of MJIN2.

Copyright (C) 2012-2013 Opensource Game Studio

This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#include <mjin2/Common.h>
#include <mjin2/ConfigFile.h>
#include <mjin2/RegExp.h>
#include <mjin2/priv/Common.h>

#include <osg/Vec3>
#include <osg/Quat>

#include <mt19937ar/mt19937ar.h>

#include <algorithm>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dirent.h>
#include <fstream>
#include <errno.h>
#include <sys/stat.h>

#ifdef WIN32
    #include <windows.h>
#elif __APPLE__
    #include <TargetConditionals.h>
#endif

namespace mjin2
{

String buildInfo() throw ()
{
    // Compile options.
    String opts;
#ifdef BUILD_WITH_GPROF
    opts += "+";
#else
    opts += "-";
#endif
    opts += "BUILD_WITH_GPROF ";
    // Build info + compile options.
    return MJIN2_STR("MJIN2-%s Build: %s GCC-%s %s. Compile options: %s",
                     MJIN2_VERSION,
                     __DATE__,
                     __VERSION__,
#ifdef WIN32
                     "Windows32"
#elif TARGET_OS_IPHONE
                     "iOS"
#elif TARGET_OS_MAC
                     "OS X"
#elif defined(_LP64)
                     "Linux64"
#else
                     "Linux32"
#endif
                     , opts.c_str());
}

Vec4 degreeToQuaternion(const Vec3 &degree) throw ()
{
    osg::Quat q;
    q.makeRotate(osg::DegreesToRadians(degree.x), osg::Vec3(1, 0, 0),
                 osg::DegreesToRadians(degree.y), osg::Vec3(0, 1, 0),
                 osg::DegreesToRadians(degree.z), osg::Vec3(0, 0, 1));
    return Vec4(q.x(), q.y(), q.z(), q.w());
}

String distribution() throw (Exception)
{
#ifdef WIN32
    StringList list;
    if (cmd("ver", list))
    {
        String s = strJoin("", list);
        return strReplace(s, "\n", "");
    }
    else
        return "Windows Unknown";
#elif __APPLE__
    StringList list;
    if (cmd("sw_vers", list))
    {
        String s = strJoin("", list);
        return strReplace(s, "(\n|\t)", " ");
    }
    else
        return "Apple Unknown";
#else
    String name;
    StringList list;
    // Read "/etc/os-release".
    if (pathExists("/etc/os-release"))
    {
        ConfigFile cfg("/etc/os-release");
        name = cfg.get("PRETTY_NAME");
    }
    // Try "lsb_release" command.
    if (!name.length() && cmd("lsb_release -d", list))
    {
        RegExp re("Description:\t(.+)\n");
        StringList o;
        if (re.match(strJoin("", list), &o))
            name = o[0];
    }
    // Try /etc/*release or /etc/*version.
    if (!name.length())
    {
        StringList list = listFiles("/etc");
        list = filterList(list, "release$|version$");
        for (StringList::iterator it = list.begin(); it != list.end(); it++)
        {
            String s = "/etc/" + *it;
            std::ifstream in(s.c_str());
            if (in.is_open())
            {
                getline(in, s);
                s = *it + " " + s;
                name = strReplace(s, "\n", "");
            }
        }
    }
    if (!name.length())
        name = "Linux Unknown";
    return name;
#endif
}

bool cmd(const String &command, StringList &output) throw ()
{
    String s = command + " 2>&1";
    FILE *pipe = popen(s.c_str(), "r");
    if (!pipe)
        return false;
    char buf[priv::MAX_STRLEN];
    while (!feof(pipe))
        if (fgets(buf, priv::MAX_STRLEN, pipe))
            output.push_back(buf);
    if (pclose(pipe))
        return false;
    return true;
}

void execute(const String &command) throw ()
{
#ifdef WIN32
    ShellExecuteA(0, "open", command.c_str(), 0, 0, SW_SHOW);
#else
    String cmd = "xdg-open ";
    cmd += command;
    std::system(cmd.c_str());
#endif
}

//! Filter list according to the pattern.
StringList filterList(const StringList &list,
                      const String     &pattern,
                      bool             caseSensitive) throw (Exception)
{
    StringList o;
    StringList::const_iterator it = list.begin();
    RegExp re(pattern, caseSensitive);
    for (; it != list.end(); ++it)
        if (re.match(*it))
            o.push_back(*it);
    return o;
}

/*
String locale(String *language,
              String *country,
              String *modifier) {
    String l;
    String c;
    String m;
    String localeName;
#ifdef WIN32
    LCID lcid = GetThreadLocale();
    char lang[MAX_STRLEN];
    char ctry[MAX_STRLEN];
    if (!GetLocaleInfo(lcid, LOCALE_SISO639LANGNAME, lang, MAX_STRLEN - 1))
        MJIN2_EXCEPTION("Cannot get locale", "Cannot get locale language");
    if (!GetLocaleInfo(lcid, LOCALE_SISO3166CTRYNAME, ctry, MAX_STRLEN - 1))
        MJIN2_EXCEPTION("Cannot get locale", "Cannot get locale country");
    l = lang;
    c = ctry;
    // No modifier for Windows.
    localeName = l + "_" + c;
#else
    localeName = setlocale(LC_MESSAGES, 0);
    String tmp = localeName;
    RegExp re("(.+)_(.+)\\.?.+?@?(.+?)");
    StringList matches;
    if (re.match(tmp, &matches)) {
        if (matches.size() == 3)
            m = matches[2];

    }
    // Get modifier if any.
    StringList list = StringUtil::split(tmp, "@");
    // Modifier is present.
    if (list.size() == 2) {
        m = list[1];
        // Remove modifier for later processing.
        tmp = StringUtil::replaceAll(tmp, "@" + m, "");
    }
    // Remove encoding if any.
    list = StringUtil::split(tmp, ".");
    if (list.size() == 2)
        tmp = StringUtil::replaceAll(tmp, "." + list[1], "");
    // Get language and country if any.
    list = StringUtil::split(tmp, "_");
    // Both language and country are present.
    if (list.size() == 2) {
        l = list[0];
        c = list[1];
    }
    // Only langauge is present.
    else if (list.size())
        l = tmp;
#endif
    if (l.length() && language)
        *language = l;
    if (c.length() && country)
        *country  = c;
    if (m.length() && modifier)
        *modifier = m;
    return localeName;
}
*/

StringList listFiles(const String &dir) throw (Exception)
{
    StringList list;
#ifdef WIN32
    String path = dir;
    path += "/*";
    struct _finddata_t file;
    s32 hFile;
    if ((hFile = _findfirst(path.c_str(), &file)) != -1)
        do
        {
            list.push_back(file.name);
        }
        while (_findnext(hFile, &file) == 0);
    _findclose(hFile);
#else
    struct dirent **namelist;
    s32 n = scandir(dir.c_str(), &namelist, 0, alphasort);
    if (n < 0)
        MJIN2_EXCEPTION("Cannot get file list",
                        MJIN2_STR("scandir(%s): %s", dir.c_str(), strerror(errno)));
    for (s32 i = 0; i < n; ++i)
    {
        list.push_back(namelist[i]->d_name);
        free(namelist[i]);
    }
    free(namelist);
#endif
    return list;
}

void mkdir(const String &dir) throw (Exception)
{
    if (pathExists(dir))
        return;
    String path;
#ifdef WIN32
    StringList list = strSplit("\\", dir);
    for (StringList::iterator it = list.begin(); it != list.end(); ++it)
    {
        if (!path.empty())
            path += "\\" + *it;
        else
            path = *it;
        if (!pathExists(path))
            priv::makeSingleDir(path);
    }
#else
    StringList list = strSplit(dir, "/");
    for (StringList::iterator it = list.begin(); it != list.end(); ++it)
    {
        path += "/" + *it;
        if (!pathExists(path))
            priv::makeSingleDir(path);
    }
#endif
}

bool pathExists(const String &path) throw (Exception)
{
#ifdef WIN32
    // Drive letter always exists.
    if (path.length() == 2 && path[1] == ':')
        return true;
#endif
    struct stat st;
    return stat(path.c_str(), &st) == 0;
}

u32 rand(bool randomize) throw ()
{
    u32 val = genrand_int32();
    return randomize ? val * time(0) : val;
}

void removePath(const String &path) throw (Exception)
{
    if (::remove(path.c_str()) < 0)
        MJIN2_EXCEPTION("Cannot remove file or directory",
                        MJIN2_STR("remove(%s): %s", path.c_str(), strerror(errno)));
}

Vec4 rotationSum(const Vec4 &r1, const Vec4 &r2) throw ()
{
    return Vec4(r1.w * r2.x      + r1.x * r2.w     + r1.y * r2.z     - r1.z * r2.y,
                r1.w * r2.y      - r1.x * r2.z     + r1.y * r2.w     - r1.z * r2.x,
                r1.w * r2.z      + r1.x * r2.y     - r1.y * r2.x     + r1.z * r2.w,
                r1.w * r2.w      - r1.x * r2.x     - r1.y * r2.y     - r1.z * r2.z);
}

void srand(u32 seed) throw ()
{
    init_genrand(seed);
}

String strJoin(const String &separator, const StringList &list) throw ()
{
    String s;
    StringList::const_iterator it = list.begin();
    bool firstElement;
    for (; it != list.end(); ++it)
    {
        if (!firstElement)
            s += separator;
        else
            firstElement = false;
        s += *it;
    }
    return s;
}

bool strMatch(const String &string,
              const String &pattern,
              bool         caseSensitive) throw (Exception)
{
    RegExp re(pattern, caseSensitive);
    return re.match(string);
}

String strReplace(const String &string,
                  const String &pattern,
                  const String &replacement,
                  bool         all,
                  bool         caseSensitive) throw (Exception)
{
    RegExp re(pattern, caseSensitive);
    return re.replace(string, replacement, all);
}

StringList strSplit(const String &string,
                    const String &pattern,
                    bool         caseSensitive) throw (Exception)
{
    RegExp re(pattern, caseSensitive);
    return re.split(string);
}

String vaToStr(const char *fmt, ...) throw ()
{
    va_list args;
    char msg[priv::MAX_STRLEN];
    va_start(args, fmt);
    vsnprintf(msg, priv::MAX_STRLEN, fmt, args);
    va_end(args);
    return msg;
}

String version()
{
    return MJIN2_VERSION;
}

} // namespace mjin2

